﻿//-----------------------------------------------------------------------
// <copyright file="GroupEntity.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Scene.Entity.Group
{
    using TileTool.Common.Math;
    using TileTool.Render;
    using TileTool.Settings;

    /// <summary>
    /// Group entity which acts as a container for many entities.
    /// </summary>
    public class GroupEntity : EntityBase
    {
        #region Constants

        /// <summary>
        /// Component name to use for the group entity.
        /// </summary>
        public const string ComponentName = "GroupEntityDummyComponent";

        #endregion

        #region Fields - Private

        /// <summary>
        /// Tracks if the group is open or not.
        /// </summary>
        private bool open = false;

        /// <summary>
        /// Set to true if we should ignore child changes.
        /// </summary>
        private bool ignoreChildChanged = false;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="createData">Creation data for the entity.</param>
        public GroupEntity(EntityCreateData createData) : 
            base(createData)
        {
            this.EditableBag.CreateValue<string>(EntityDataBagKeys.ComponentName, ComponentName);
        }

        #endregion

        #region Properties - Public

        /// <summary>
        /// Gets if this entity can be sized by a manipulation tool.
        /// </summary>
        public override bool CanSize
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets or sets if the group is open or not. If a group is closed, the objects
        /// inside of it cannot be selected. If a group is open, the objects inside of
        /// it can be selected.
        /// </summary>
        public bool Open
        {
            get
            {
                return this.open;
            }

            set
            {
                if (this.open == value)
                {
                    return;
                }

                this.open = value;

                this.TriggerChanged();
            }
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Draw the entity with the passed renderer.
        /// </summary>
        /// <param name="renderData">Rendering data. Always non-null.</param>
        public override void Draw(IRenderData renderData)
        {
            base.Draw(renderData);

            if (!this.Selected && !this.DescendantSelected)
            {
                return;
            }

            ColorRGBA drawColor;
            GroupEntitySettings settings = SettingsSystem.Get<GroupEntitySettings>();

            if (this.Open)
            {
                drawColor = settings.OpenGroupColor;
            }
            else
            {
                drawColor = settings.ClosedGroupColor;
            }

            renderData.Renderer.DrawLineRect(Vector2D.Zero, this.Size, drawColor, 1.0f);
        }

        /// <summary>
        /// Called to draw the entity if it is selected.
        /// </summary>
        /// <param name="renderData">Rendering data. Always non-null.</param>
        public override void DrawSelected(IRenderData renderData)
        {
            base.DrawSelected(renderData);
        }

        #endregion

        #region Methods - Public - Parent Child

        /// <summary>
        /// Add the passed entity as a child to this entity.
        /// </summary>
        /// <param name="entity">Entity to add.</param>
        public override void AddChild(IEntity entity)
        {
            base.AddChild(entity);
        }

        /// <summary>
        /// Remove the passed entity from this one as a child.
        /// </summary>
        /// <param name="entity">Entity to remove.</param>
        public override void RemoveChild(IEntity entity)
        {
            base.RemoveChild(entity);
        }

        #endregion

        #region Methods - Protected

        /// <summary>
        /// Called when this entity's matrix changes.
        /// </summary>
        protected override void OnMatrixChanged()
        {
            this.ignoreChildChanged = true;

            base.OnMatrixChanged();

            this.ignoreChildChanged = false;
        }

        /// <summary>
        /// Handle when one of our children changes.
        /// </summary>
        /// <param name="args">Event argumnets.</param>
        protected override void HandleChildChanged(EntityChangedEventArgs args)
        {
            base.HandleChildChanged(args);

            if (this.ignoreChildChanged)
            {
                return;
            }

            switch (args.Property)
            {
                case EntityChangedProperty.Matrix:
                case EntityChangedProperty.Size:
                    break;
                default:
                    return;
            }

            this.UpdateMatrixAndSize();
        }

        /// <summary>
        /// Called when a child entity was added.
        /// </summary>
        /// <param name="child">Child that was added.</param>
        protected override void OnChildAdded(IEntity child)
        {
            base.OnChildAdded(child);

            this.UpdateMatrixAndSize();
        }

        /// <summary>
        /// Called when a child entity was removed.
        /// </summary>
        /// <param name="child">Child that was removed.</param>
        protected override void OnChildRemoved(IEntity child)
        {
            base.OnChildRemoved(child);

            this.UpdateMatrixAndSize();
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Update the matrix and size for the group entity.
        /// </summary>
        private void UpdateMatrixAndSize()
        {
            this.ignoreChildChanged = true;

            IEntityCollection children = this.Children;
            Rect2D childrenRect = EntityUtility.GetBoundingRect(children);

            // First, clear out the parent matrix for all children.
            foreach (IEntity entity in children)
            {
                entity.ParentMatrix = Matrix2D.Identity;
            }

            // Figure out group entity's new matrix.
            Matrix2D newMatrix = Matrix2D.Identity;

            newMatrix.Pos = childrenRect.Pos;

            Matrix2D newMatrixInv = newMatrix.Invert();

            // Replace the parent matrix for all children.
            foreach (IEntity entity in children)
            {
                entity.ParentMatrix = newMatrixInv * entity.Matrix;
            }

            this.Size = childrenRect.Size;
            this.Matrix = newMatrix;

            this.ignoreChildChanged = false;
        }

        #endregion
    }
}
